/*
 * Copyright 2005-2006 Sun Microsystems, Inc.  All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Sun designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Sun in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
 * CA 95054 USA or visit www.sun.com if you need additional information or
 * have any questions.
 */
package sun.tools.attach;

import com.sun.tools.attach.AgentInitializationException;
import com.sun.tools.attach.AgentLoadException;
import com.sun.tools.attach.VirtualMachine;
import com.sun.tools.attach.spi.AttachProvider;

import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

/**
 * The HotSpot implementation of com.sun.tools.attach.VirtualMachine.
 */
public abstract class HotSpotVirtualMachine extends VirtualMachine
{
   HotSpotVirtualMachine(AttachProvider provider, String id)
   {
      super(provider, id);
   }

   /**
    * Load agent library.
    * If isAbsolute is true then the agent library is the absolute path to the library and thus
    * will not be expanded in the target VM.
    * If isAbsolute is false then the agent library is just a library name and it will be expended
    * in the target VM.
    */
   private void loadAgentLibrary(String agentLibrary, boolean isAbsolute, String options)
      throws AgentLoadException, AgentInitializationException, IOException
   {
      InputStream in = execute("load", agentLibrary, isAbsolute ? "true" : "false", options);

      try {
         int result = readInt(in);

         if (result != 0) {
            throw new AgentInitializationException("Agent_OnAttach failed", result);
         }
      }
      finally {
         in.close();
      }
   }

   /**
    * Load agent library - library name will be expanded in target VM.
    */
   @Override
   public void loadAgentLibrary(String agentLibrary, String options)
      throws AgentLoadException, AgentInitializationException, IOException
   {
      loadAgentLibrary(agentLibrary, false, options);
   }

   /**
    * Load agent - absolute path of library provided to target VM.
    */
   @Override
   public void loadAgentPath(String agentLibrary, String options)
      throws AgentLoadException, AgentInitializationException, IOException
   {
      loadAgentLibrary(agentLibrary, true, options);
   }

   /**
    * Load JPLIS agent which will load the agent JAR file and invoke the agentmain method.
    */
   @Override
   public void loadAgent(String agent, String options)
      throws AgentLoadException, AgentInitializationException, IOException
   {
      String args = agent;

      if (options != null) {
         args = args + '=' + options;
      }

      try {
         loadAgentLibrary("instrument", args);
      }
      catch (AgentLoadException ignore) {
         throw new InternalError("instrument library is missing in target VM");
      }
      catch (AgentInitializationException x) {
         /*
          * Translate interesting errors into the right exception and message
          * (FIXME: create a better interface to the instrument implementation so this isn't
          * necessary).
          */
         int rc = x.returnValue();

         switch (rc) {
            case JNI_ENOMEM:
               throw new AgentLoadException("Insufficient memory");
            case ATTACH_ERROR_BADJAR:
               throw new AgentLoadException("Agent JAR not found or no Agent-Class attribute");
            case ATTACH_ERROR_NOTONCP:
               throw new AgentLoadException("Unable to add JAR file to system class path");
            case ATTACH_ERROR_STARTFAIL:
               throw new AgentInitializationException(
                  "Agent JAR loaded but agent failed to initialize");
            default:
               throw new AgentLoadException("Failed to load agent - unknown reason: " + rc);
         }
      }
   }

   /*
    * The possible errors returned by JPLIS's agentmain.
    */
   private static final int JNI_ENOMEM = -4;
   private static final int ATTACH_ERROR_BADJAR = 100;
   private static final int ATTACH_ERROR_NOTONCP = 101;
   private static final int ATTACH_ERROR_STARTFAIL = 102;

   /**
    * Send "properties" command to target VM.
    */
   @Override
   public Properties getSystemProperties() throws IOException
   {
      InputStream in = null;
      Properties props = new Properties();

      try {
         in = executeCommand("properties");
         props.load(in);
      }
      finally {
         if (in != null) {
            in.close();
         }
      }

      return props;
   }

   @Override
   public Properties getAgentProperties() throws IOException
   {
      InputStream in = null;
      Properties props = new Properties();

      try {
         in = executeCommand("agentProperties");
         props.load(in);
      }
      finally {
         if (in != null) {
            in.close();
         }
      }

      return props;
   }

   // --- HotSpot specific methods ---

   // same as SIGQUIT
   public void localDataDump() throws IOException
   {
      executeCommand("datadump").close();
   }

   // Remote ctrl-break. The output of the ctrl-break actions can be read from the input stream.
   public InputStream remoteDataDump(Object... args) throws IOException
   {
      return executeCommand("threaddump", args);
   }

   // Remote heap dump. The output (error message) can be read from the returned input stream.
   public InputStream dumpHeap(Object... args) throws IOException
   {
      return executeCommand("dumpheap", args);
   }

   // Heap histogram (heap inspection in HotSpot).
   public InputStream heapHisto(Object... args) throws IOException
   {
      return executeCommand("inspectheap", args);
   }

   // Set JVM command line flag.
   public InputStream setFlag(String name, String value) throws IOException
   {
      return executeCommand("setflag", name, value);
   }

   // Print command line flag.
   public InputStream printFlag(String name) throws IOException
   {
      return executeCommand("printflag", name);
   }

   // -- Supporting methods

   /**
    * Execute the given command in the target VM - specific platform implementation must implement
    * this.
    */
   abstract InputStream execute(String cmd, Object... args) throws AgentLoadException, IOException;

   /**
    * Convenience method for simple commands.
    */
   private InputStream executeCommand(String cmd, Object... args) throws IOException
   {
      try {
         return execute(cmd, args);
      }
      catch (AgentLoadException ignore) {
         throw new InternalError("Should not get here");
      }
   }


   /**
    * Utility method to read an 'int' from the input stream.
    * Ideally we should be using java.util.Scanner here but this implementation guarantees not to
    * read ahead.
    */
   int readInt(InputStream in) throws IOException
   {
      StringBuilder sb = new StringBuilder();

      // read to \n or EOF
      int n;
      byte[] buf = new byte[1];

      do {
         n = in.read(buf, 0, 1);

         if (n > 0) {
            char c = (char) buf[0];

            if (c == '\n') {
               break;                  // EOL found
            }
            else {
               sb.append(c);
            }
         }
      }
      while (n > 0);

      if (sb.length() == 0) {
         throw new IOException("Premature EOF");
      }

      int value;

      try {
         value = Integer.parseInt(sb.toString());
      }
      catch (NumberFormatException ignore) {
         throw new IOException("Non-numeric value found - int expected");
      }

      return value;
   }

   // -- attach timeout support

   private static final long defaultAttachTimeout = 5000;
   private volatile long attachTimeout;

   /**
    * Return attach timeout based on the value of the sun.tools.attach.attachTimeout
    * property, or the default timeout if the property is not set to a positive value.
    */
   long attachTimeout()
   {
      if (attachTimeout == 0) {
         synchronized (this) {
            if (attachTimeout == 0) {
               try {
                  String s = System.getProperty("sun.tools.attach.attachTimeout");
                  attachTimeout = Long.parseLong(s);
               }
               catch (SecurityException ignore) {}
               catch (NumberFormatException ignore) {}

               if (attachTimeout <= 0) {
                  attachTimeout = defaultAttachTimeout;
               }
            }
         }
      }

      return attachTimeout;
   }
}
